package sip

import (
	"bufio"
	"bytes"
	"fmt"
	"log"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"

	"gitee.com/larry_wan/go-lib/etree"
	"gitee.com/larry_wan/go-lib/events"
	"gitee.com/larry_wan/go-lib/siprocket"
	"gitee.com/larry_wan/go-lib/utils"
	"github.com/patrickmn/go-cache"
)

type Server struct {
	Serial string
	Name   string
	Realm  string
	Host   string
	Port   uint
	WanIP  string
	StreamUrlIP  string
	Transport    string
	Load         uint
	RegistInterval    uint
	AckTimeout        uint
	KeepaliveTimeout  uint
	StreamTimeout     uint
	StreamAuthURL     string
	DevCache          *cache.Cache `json:"-" structs:"-"`
	CmdCache          *cache.Cache `json:"-" structs:"-"`
	SessionCache      *cache.Cache `json:"-" structs:"-"`
	StreamCache       *cache.Cache `json:"-" structs:"-"`
	Quit              chan bool `json:"-" structs:"-"`
	Stoped            bool `json:"-" structs:"-"`

	handleMap     map[string][]HandleFunc
	handleMapLock sync.RWMutex

	ackDispatcher *events.EventDispatcher
	udpConn       *net.UDPConn
	tcpListener   *net.TCPListener
	//sip server tcpclient存在多个连接
	tcpConn map[string]*net.TCPConn //*net.TCPConn

	cseq             uint
	TalkChan         chan (string)            `json:"-" structs:"-"`
	TalkChanData     map[string]chan (string) `json:"-" structs:"-"`
	TalkChanDataLock sync.RWMutex             `json:"-" structs:"-"`
	Talk             bool                     `json:"-" structs:"-"`

	UacAudioUdpConn map[string]*net.UDPConn `json:"-" structs:"-"`
	UacAudioOnOff   map[string]chan (bool)  `json:"-" structs:"-"`
	UacAudioLock    sync.RWMutex            `json:"-" structs:"-"`
}

func (s *Server) String() string {
	return fmt.Sprintf("SIP Server[%s]", s.Serial)
}

func NewServer(serial, realm string, port uint) *Server {
	sec := utils.Conf().Section("sip")
	host := sec.Key("host").MustString(utils.LocalIP())
	hostAndPort := fmt.Sprintf("%s:%d", host, port)
	streamUrlIP := utils.Conf().Section("sms").Key("stream_url_ip").MustString("")
	ackTimeout := sec.Key("ack_timeout").MustUint(15)
	keepaliveTimeout := sec.Key("keepalive_timeout").MustUint(100)
	streamTimeout := sec.Key("stream_timeout").MustUint(60)
	registInterval := sec.Key("regist_interval").MustUint(15)
	uas := &Server{
		Serial:           serial,
		Name:             sec.Key("name").MustString(hostAndPort),
		Realm:            realm,
		Host:             sec.Key("host").MustString(host),
		WanIP:            sec.Key("wan_ip").MustString(host),
		Port:             port,
		Transport:        sec.Key("transport").MustString("udp"),
		AckTimeout:       ackTimeout,
		KeepaliveTimeout: keepaliveTimeout,
		StreamTimeout:    streamTimeout,
		StreamAuthURL:    sec.Key("stream_auth_url").MustString(""),
		RegistInterval:   registInterval,
		DevCache:         cache.New(time.Duration(keepaliveTimeout)*time.Second, 10*time.Second),
		CmdCache:         cache.New(time.Duration(keepaliveTimeout)*time.Second, 10*time.Second),
		SessionCache:     cache.New(time.Duration(keepaliveTimeout)*time.Second, 10*time.Second),
		StreamCache:      cache.New(time.Duration(streamTimeout)*time.Second, 10*time.Second),
		Quit:             make(chan bool),
		TalkChan:         make(chan string),
		TalkChanData:     make(map[string]chan string),
		handleMap:        make(map[string][]HandleFunc),
		ackDispatcher:    events.NewDispatcher(),

		UacAudioUdpConn: make(map[string]*net.UDPConn),
		UacAudioOnOff:   make(map[string]chan bool),
	}
	//流地址单独配置
	if streamUrlIP != "" {
		uas.StreamUrlIP = streamUrlIP
	} else {
		uas.StreamUrlIP = uas.WanIP
	}

	uas.StreamCache.OnEvicted(func(streamid string, _stream interface{}) {
		stream, ok := _stream.(*Stream)
		if !ok {
			log.Printf("stream[%s] cache type convert failed", streamid)
			return
		}
		log.Printf("stream[%s] cache OnEvicted", streamid)
		if err := uas.ByeDevice(stream); err != nil {
			log.Printf("bye device error, %v", err)
		}
		/*
		if err := uas.ByeMediaServer(stream); err != nil {
			log.Printf("bye media[%v] server error, %v", streamid, err)
		}
		redis.Client.Del(streamid)
		*/
	})
	return uas
}

func (server *Server) HandleRequest(req *Request, sipMsg *siprocket.SipMsg, transport *Transport) {
	handles := make([]HandleFunc, 0)
	server.handleMapLock.RLock()
	for _, handle := range server.handleMap[req.Method] {
		handles = append(handles, handle)
	}
	server.handleMapLock.RUnlock()
	c := &Context{
		Server:    server,
		Transport: transport,
		Req:       req,
		SipMsg:    sipMsg,
		abort:     false,
	}
	for _, handle := range handles {
		if c.abort {
			break
		}
		handle(c)
	}
}

func (server *Server) HandleResponse(name string, response *Response) {
    server.ackDispatcher.Dispatch(name, response)
}

func (server *Server) Handle(method string, f HandleFunc) {
	server.handleMapLock.Lock()
	if _, ok := server.handleMap[method]; !ok {
		server.handleMap[method] = make([]HandleFunc, 0)
	}
	server.handleMap[method] = append(server.handleMap[method], f)
	server.handleMapLock.Unlock()
}

func (server *Server) WaitAck(callid, cseq string) (res *Response, err error) {
	ch := make(chan *Response, 3)
	l := events.NewListener(fmt.Sprintf("%s:%s", callid, cseq), func(args ...interface{}) bool {
		if len(args) != 1 {
			log.Printf("ack dispatch with wrong length %d", len(args))
			return false
		}
		//fmt.Printf("WaitAck===========args=%v\n", args)
		res, ok := args[0].(*Response)
		if !ok {
			log.Printf("ack dispatch with wrong type %v", args[0])
			return false
		}
		ch <- res
		return true
	})
	timer := time.NewTimer(time.Duration(server.AckTimeout) * time.Second)
	defer timer.Stop()
	server.ackDispatcher.AddListener(l)
	defer server.ackDispatcher.RemoveListener(l)
	for {
		select {
		case _res := <-ch:
			if _res.StatusCode < 200 { // Trying
				log.Printf("Wait Ack tring......")
				continue
			}
			res = _res
			return
		case <-timer.C:
			err = fmt.Errorf("等待ACK请求： callid[%s] cseq[%s] 超时[%ds]", callid, cseq, server.AckTimeout)
			return
		}
	}
}

func (server *Server) Start() (err error) {
	udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", server.Port))
	if err != nil {
		return
	}
	conn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		return
	}
	if e := conn.SetReadBuffer(UDP_BUF_SIZE); e != nil {
		log.Printf("sip server set udp connection read buffer error, %v", e)
	}
	if e := conn.SetWriteBuffer(UDP_BUF_SIZE); e != nil {
		log.Printf("udp server set udp connection write buffer error, %v", e)
	}
	server.udpConn = conn

	//new add tcp sip server
	tcpAddr, e := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", server.Port))
	if e != nil {
		return
	}
	listener, e := net.ListenTCP("tcp", tcpAddr)
	if e != nil {
		return
	}
	server.tcpListener = listener

	go server.runTCP()
	go server.runUDP()
	return
}

func (server *Server) runUDP() {
	log.Printf("sip server start --> udp://%s:%d", server.Host, server.Port)
	defer log.Printf("sip server stop --> udp://%s:%d", server.Host, server.Port)
	bufUDP := make([]byte, UDP_BUF_SIZE)
	for !server.Stoped {

		n, udpAddr, err := server.udpConn.ReadFromUDP(bufUDP)
		if err != nil {
			log.Printf("udp read error, %v", err)
			continue
		}
		transport := server.NewUDPTransport(udpAddr, server.udpConn)
		sipBuf := bufUDP[:n]
		sipRaw := string(sipBuf)
		sipMsg := siprocket.Parse(sipBuf)
		statusCode := string(sipMsg.Req.StatusCode)
		contentLen, _ := strconv.Atoi(string(sipMsg.ContLen.Value))
		//解决接收数据不全报错的问题
		if (len(sipRaw) - contentLen) < 0 {
			continue
		}
		header := sipRaw[:len(sipRaw)-contentLen]
		body := sipRaw[len(sipRaw)-contentLen:]
		//fmt.Printf("ContType ====%s\n", string(sipMsg.ContType.Value))
		if strings.ToLower(string(sipMsg.ContType.Value)) == "application/sdp" {
			pos := strings.Index(sipRaw, "\r\n\r\n")
			if pos > -1 && pos <= (len(sipRaw)-contentLen) {
				header = sipRaw[:pos+4]
				body = sipRaw[pos+4:]
			}
			fmt.Printf("sdp-----------pos: %d  %d\n", pos, len(sipRaw)-contentLen)
		}
		Logf("udp接收1：[%s]<<<<<<[%s:%d]\r\n%v", server.udpConn.LocalAddr().String(), udpAddr.IP, udpAddr.Port, sipRaw)
		if statusCode == "" { // request
			req, err := UnmarshalRequest(header, body)
			if err != nil {
				//log.Printf("unmarshal request failed, %v", err)
				continue
			}
			req.FixVia(transport)
			server.HandleRequest(req, &sipMsg, transport)
		} else { // response
			res, err := UnmarshalResponse(header, body)
			if err != nil {
				//log.Printf("unmarshal response failed, %v", err)
				continue
			}
			callid := res.HeaderMap["Call-ID"]
			cseq := res.HeaderMap["CSeq"]
			server.ackDispatcher.Dispatch(fmt.Sprintf("%s:%s", callid, cseq), res)
		}
	}
}

func (server *Server) runTCP() {
	log.Printf("sip server start --> tcp://%s:%d", server.Host, server.Port)
	defer log.Printf("sip server stop --> tcp://%s:%d", server.Host, server.Port)

	for !server.Stoped {
		conn, err := server.tcpListener.AcceptTCP()
		if err != nil {
			return
		}
		if e := conn.SetReadBuffer(UDP_BUF_SIZE); e != nil {
			log.Printf("sip tcp conn set read buffer error, %v", e)
		}
		if e := conn.SetWriteBuffer(UDP_BUF_SIZE); e != nil {
			log.Printf("sip tcp conn set write buffer error, %v", e)
		}
		//server.TCPConn = conn
		//log.Log.Printf("new sip tcp connection coming, %v", server.TCPConn.RemoteAddr())
		//connRW := bufio.NewReadWriter(bufio.NewReader(server.TCPConn), bufio.NewWriter(server.TCPConn))
		//server.connRW = connRW
		go server.handleTCPConnection(conn)
	}
}

func (server *Server) handleTCPConnection(con *net.TCPConn) {
	//defer server.TCPConn.Close()
	// conn,err := net.Dial("tcp", server.Cascade)
	// if err != nil {
	// 	sms_log.Log.Printf("Dial tcp con error, %v", err)
	// }
	// timeoutTCPConn := &RichConn{conn, time.Duration(28800) * time.Millisecond}
	// connRW := bufio.NewReadWriter(bufio.NewReaderSize(timeoutTCPConn, 204800), bufio.NewWriterSize(timeoutTCPConn, 204800))
	strAddr := con.RemoteAddr().String()
	log.Printf(":::::::::::new sip tcp connection coming, %s", strAddr)

	tcpAddr, err := net.ResolveTCPAddr("tcp", strAddr)
	if err != nil {
		return
	}
	//connRW := bufio.NewReadWriter(bufio.NewReader(con), bufio.NewWriter(con))

	//bufLen := make([]byte, 2)
	sipBytes := make([]byte, UDP_BUF_SIZE)
	for !server.Stoped {
		// if _, err := io.ReadFull(connRW, bufLen); err != nil {
		// 	log.Printf("read tcp sip length bytes error, %v", err)
		// 	con.Close()
		// 	break
		// }
		// rtpLen := binary.BigEndian.Uint16(bufLen)
		// rtpBytes := make([]byte, rtpLen)
		// if _, err := io.ReadFull(connRW, rtpBytes); err != nil {
		// 	log.Printf("read tcp sip bytes error, %v", err)
		// 	con.Close()
		// 	break
		// }

		//n, err := io.ReadFull(connRW, sipBytes)
		n, err := con.Read(sipBytes)
		if err != nil {
			if err.Error() == "EOF" {
				log.Printf("read tcp sip bytes error, %v", err)
				con.Close()
				break
			}
			log.Printf("read tcp sip bytes error, %v", err)
			con.Close()
			break
			//continue
		}
		transport := server.NewTCPTransport(tcpAddr, con)
		sipBuf := sipBytes[:n]
		sipRaw := string(sipBuf)
		sipMsg := siprocket.Parse(sipBuf)
		statusCode := string(sipMsg.Req.StatusCode)
		contentLen, _ := strconv.Atoi(string(sipMsg.ContLen.Value))
		//Logf("tcp接收111111：[%s]<<<<<<[%s:%d]\r\n%v\r\ncontentLen=%d\r\n", con.LocalAddr().String(), tcpAddr.IP, tcpAddr.Port, sipRaw, contentLen)
		//解决接收数据不全报错的问题
		if (len(sipRaw) - contentLen) < 0 {
			continue
		}

		header := sipRaw[:len(sipRaw)-contentLen]
		body := sipRaw[len(sipRaw)-contentLen:]
		//fmt.Printf("contentLen: %d, header: %s, body: %s\n\n", contentLen, header, body)
		Logf("tcp接收1：[%s]<<<<<<[%s:%d]\r\n%v", con.LocalAddr().String(), tcpAddr.IP, tcpAddr.Port, sipRaw)
		if statusCode == "" { // request
			req, err := UnmarshalRequest(header, body)
			if err != nil {
				log.Printf("unmarshal request failed, %v", err)
				continue
			}
			req.FixVia(transport)
			server.HandleRequest(req, &sipMsg, transport)
		} else { // response
			res, err := UnmarshalResponse(header, body)
			if err != nil {
				log.Printf("unmarshal response failed, %v", err)
				continue
			}
			callid := res.HeaderMap["Call-ID"]
			cseq := res.HeaderMap["CSeq"]
			server.ackDispatcher.Dispatch(fmt.Sprintf("%s:%s", callid, cseq), res)
		}
	}
}

func (server *Server) Stop() {
	server.Stoped = true
	if server.StreamCache != nil {
		for key, _ := range server.StreamCache.Items() {
			server.StreamCache.Delete(key)
		}
	}
	if server.SessionCache != nil {
		for key, _ := range server.SessionCache.Items() {
			server.SessionCache.Delete(key)
		}
	}
	if server.CmdCache != nil {
		for key, _ := range server.CmdCache.Items() {
			server.CmdCache.Delete(key)
		}
	}
	if server.DevCache != nil {
		for key, _ := range server.DevCache.Items() {
			server.DevCache.Delete(key)
		}
	}
	if server.Quit != nil {
		close(server.Quit)
	}
	if server.udpConn != nil {
		server.udpConn.Close()
	}
	if server.tcpListener != nil {
		server.tcpListener.Close()
	}
}

func (server *Server) GetDevice(serial string) *Device {
	_dev, ok := server.DevCache.Get(serial)
	if !ok {
		return nil
	}
	dev, ok := _dev.(*Device)
	if !ok {
		return nil
	}
	return dev
}

func (server *Server) NewUDPTransport(remoteAddr *net.UDPAddr, conn *net.UDPConn) (transport *Transport) {
	transport = &Transport{
		Handler:     server,
		Protocol:   "UDP",
		RemoteIP:   remoteAddr.IP.String(),
		RemotePort: uint(remoteAddr.Port),
		UDPConn:    conn,
	}
	return
}

func (server *Server) NewTCPTransport(remoteAddr *net.TCPAddr, conn *net.TCPConn) (transport *Transport) {
	transport = &Transport{
		Handler:     server,
		Protocol:   "TCP",
		RemoteIP:   remoteAddr.IP.String(),
		RemotePort: uint(remoteAddr.Port),
		TCPConn:    conn,
		TcpRWriter: bufio.NewReadWriter(bufio.NewReaderSize(conn, UDP_BUF_SIZE), bufio.NewWriterSize(conn, UDP_BUF_SIZE)),
	}
	return
}

func (server *Server) DialUDPTransport(host string, port uint) (transport *Transport, err error) {
	udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		return
	}
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return
	}
	transport = server.NewUDPTransport(udpAddr, conn)
	return
}

func (server *Server) DialTCPTransport(host string, port uint) (transport *Transport, err error) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		return
	}
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	if err != nil {
		return
	}
	transport = server.NewTCPTransport(tcpAddr, conn)
	return
}

func (server *Server) MakeRequest(method, reqURI, to string, body string) (req *Request, err error) {
	buf := bytes.NewBufferString("")
	buf.WriteString(fmt.Sprintf("%s sip:%s %s\r\n", strings.ToUpper(method), reqURI, SIP_VERSION))
	buf.WriteString(fmt.Sprintf("Via: %s/%s %s:%d;rport=%d;branch=%s\r\n", SIP_VERSION, strings.ToUpper(server.Transport), server.Host, server.Port, server.Port, GenBranch()))
	buf.WriteString(fmt.Sprintf("From: <sip:%s@%s>;tag=%s\r\n", server.Serial, server.Realm, GenTag()))
	buf.WriteString(fmt.Sprintf("To: <sip:%s>\r\n", to))
	buf.WriteString(fmt.Sprintf("Call-ID: %s\r\n", GenCallID()))
	server.cseq++
	buf.WriteString(fmt.Sprintf("CSeq: %d %s\r\n", server.cseq, strings.ToUpper(method)))
	buf.WriteString(fmt.Sprintf("Max-Forwards: 70\r\n"))
	buf.WriteString(fmt.Sprintf("User-Agent: Easy SIP Server\r\n"))
	req, err = UnmarshalRequest(buf.String(), "")
	if err != nil {
		return
	}
	req.SetBody(body)
	return
}

func (server *Server) GetSeq() uint {
	return server.cseq
}

func (server *Server) MakeCatalogRequest(c *Transport, toSerial, toRealm string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	query := doc.CreateElement("Query")
	query.CreateElement("CmdType").SetText("Catalog")
	sn = GenSN()
	query.CreateElement("SN").SetText(sn)
	query.CreateElement("DeviceID").SetText(toSerial)
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}

	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol))
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	channels := make([]*Channel, 0)
	server.CmdCache.SetDefault(sn, channels)
	return
}

func (server *Server) MakeSubscribeCatalogRequest(c *Transport, toSerial, toRealm, expires string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	query := doc.CreateElement("Query")
	query.CreateElement("CmdType").SetText("Catalog")
	sn = GenSN()
	query.CreateElement("SN").SetText(sn)
	query.CreateElement("DeviceID").SetText(toSerial)
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol))
	req, err = server.MakeRequest("SUBSCRIBE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	req.SetHeader("Event", "presence")
	req.SetHeader("Expires", expires)

	channels := make([]*Channel, 0)
	server.CmdCache.SetDefault(sn, channels)
	return
}

func (server *Server) MakeSubscribeAlarmRequest(c *Transport, toSerial, toRealm, expires string) (req *Request, sn string, err error) {
	contact := fmt.Sprintf("<sip:%s@%s:%d>", server.Serial, server.Host, server.Port)
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	query := doc.CreateElement("Query")
	query.CreateElement("CmdType").SetText("Alarm")
	sn = GenSN()
	query.CreateElement("SN").SetText(sn)
	query.CreateElement("DeviceID").SetText(toSerial)
	query.CreateElement("StartAlarmPriority").SetText("0")
	query.CreateElement("EndAlarmPriority").SetText("0")
	query.CreateElement("AlarmMethod").SetText("0")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}

	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol))
	req, err = server.MakeRequest("SUBSCRIBE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	req.SetHeader("Event", "presence")
	req.SetHeader("Expires", expires)
	req.SetHeader("Contact", contact)
	return
}
func (server *Server) MakeSubscribeMobilePositionRequest(c *Transport, toSerial, toRealm, expires string) (req *Request, sn string, err error) {
	contact := fmt.Sprintf("<sip:%s@%s:%d>", server.Serial, server.Host, server.Port)
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	query := doc.CreateElement("Query")
	query.CreateElement("CmdType").SetText("MobilePosition")
	sn = GenSN()
	query.CreateElement("SN").SetText(sn)
	query.CreateElement("DeviceID").SetText(toSerial)
	query.CreateElement("Interval").SetText("5")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}

	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol))
	req, err = server.MakeRequest("SUBSCRIBE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	req.SetHeader("Event", "presence")
	req.SetHeader("Expires", expires)
	req.SetHeader("Contact", contact)
	return
}

func (server *Server) MakeRecordInfoRequest(c *Transport, toSerial, toRealm, startTime, endTime string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	query := doc.CreateElement("Query")
	query.CreateElement("CmdType").SetText("RecordInfo")
	sn = GenSN()
	query.CreateElement("SN").SetText(sn)
	query.CreateElement("DeviceID").SetText(toSerial)
	query.CreateElement("StartTime").SetText(startTime)
	query.CreateElement("EndTime").SetText(endTime)
	query.CreateElement("Type").SetText("all")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	records := make([]*RecordInfo, 0)
	server.CmdCache.SetDefault(sn, records)
	return
}

func (server *Server) MakeTalkRequest(c *Transport, toSerial, toRealm, channelId string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="GB2312"`)
	notify := doc.CreateElement("Notify")
	notify.CreateElement("CmdType").SetText("Broadcast")
	sn = GenSN()
	notify.CreateElement("SN").SetText(sn)
	notify.CreateElement("SourceID").SetText(toSerial)
	notify.CreateElement("TargetID").SetText(channelId)

	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", channelId, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	return
}

func (server *Server) MakePTZQueryRequest(c *Transport, toSerial, toRealm string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Query")
	control.CreateElement("CmdType").SetText("PresetQuery")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	ptzInfo := make([]*PTZInfo, 0)
	server.CmdCache.SetDefault(sn, ptzInfo)
	return
}

func (server *Server) MakePTZControlRequest(c *Transport, toSerial, toRealm, ptzCmd string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Control")
	control.CreateElement("CmdType").SetText("DeviceControl")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)
	control.CreateElement("PTZCmd").SetText(ptzCmd)

	info := control.CreateElement("Info")
	info.CreateElement("ControlPriority").SetText("5")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	return
}

// 拉框放大控制命令
func (server *Server) MakeDragZoomInControlRequest(c *Transport, toSerial, toRealm string, length, width, midPointX, midPointY, lengthX, lengthY int) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Control")
	control.CreateElement("CmdType").SetText("DeviceControl")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)
	cmd := control.CreateElement("DragZoomIn")
	cmd.CreateElement("Length").SetText(fmt.Sprintf("%d", length))
	cmd.CreateElement("Width").SetText(fmt.Sprintf("%d", width))
	cmd.CreateElement("MidPointX").SetText(fmt.Sprintf("%d", midPointX))
	cmd.CreateElement("MidPointY").SetText(fmt.Sprintf("%d", midPointY))
	cmd.CreateElement("LengthX").SetText(fmt.Sprintf("%d", lengthX))
	cmd.CreateElement("LengthY").SetText(fmt.Sprintf("%d", lengthY))

	info := control.CreateElement("Info")
	info.CreateElement("ControlPriority").SetText("5")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	return
}

// 拉框缩小控制命令
func (server *Server) MakeDragZoomOutControlRequest(c *Transport, toSerial, toRealm string, length, width, midPointX, midPointY, lengthX, lengthY int) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Control")
	control.CreateElement("CmdType").SetText("DeviceControl")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)
	cmd := control.CreateElement("DragZoomOut")
	cmd.CreateElement("Length").SetText(fmt.Sprintf("%d", length))
	cmd.CreateElement("Width").SetText(fmt.Sprintf("%d", width))
	cmd.CreateElement("MidPointX").SetText(fmt.Sprintf("%d", midPointX))
	cmd.CreateElement("MidPointY").SetText(fmt.Sprintf("%d", midPointY))
	cmd.CreateElement("LengthX").SetText(fmt.Sprintf("%d", lengthX))
	cmd.CreateElement("LengthY").SetText(fmt.Sprintf("%d", lengthY))

	info := control.CreateElement("Info")
	info.CreateElement("ControlPriority").SetText("5")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	return
}

//看守位控制命令
func (server *Server) MakeGuardControlRequest(c *Transport, toSerial, toRealm string,
	Enabled, ResetTime, PresetIndex int) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Control")
	control.CreateElement("CmdType").SetText("DeviceControl")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)

	//内容填写
	cmd := control.CreateElement("HomePosition")
	cmd.CreateElement("Enabled").SetText(fmt.Sprintf("%d", Enabled))
	cmd.CreateElement("ResetTime").SetText(fmt.Sprintf("%d", ResetTime))     //秒
	cmd.CreateElement("PresetIndex").SetText(fmt.Sprintf("%d", PresetIndex)) //预置位ID

	info := control.CreateElement("Info")
	info.CreateElement("ControlPriority").SetText("5")
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	return
}

//查询设备位置
func (server *Server) MakeDevicePositionRequest(c *Transport, toSerial, toRealm string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Query")
	control.CreateElement("CmdType").SetText("MobilePosition")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	ptzInfo := make([]*PTZInfo, 0)
	server.CmdCache.SetDefault(sn, ptzInfo)
	return
}

//pass-through
func (server *Server) MakeIFameRequest(c *Transport, toSerial, toRealm, ifameCmd string) (req *Request, sn string, err error) {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
	control := doc.CreateElement("Control")
	control.CreateElement("CmdType").SetText("DeviceControl")
	sn = GenSN()
	control.CreateElement("SN").SetText(sn)
	control.CreateElement("DeviceID").SetText(toSerial)
	control.CreateElement("IFameCmd").SetText(ifameCmd)
	doc.Indent(4)
	body, err := doc.WriteToString()
	if err != nil {
		return
	}
	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", toSerial, c.RemoteIP, c.RemotePort, strings.ToLower(c.Protocol)) //c *Transport,
	req, err = server.MakeRequest("MESSAGE", reqURI, fmt.Sprintf("%s@%s", toSerial, toRealm), body)
	if err != nil {
		return
	}
	req.SetHeader("Content-Type", "Application/MANSCDP+xml")
	return
}

func (server *Server) ByeDevice(stream *Stream) (err error) {
	contact := fmt.Sprintf("<sip:%s@%s:%d>", server.Serial, server.Host, server.Port)
	_sessionDev, ok := server.SessionCache.Get(stream.DevSessionID)
	if !ok {
		err = fmt.Errorf("device session[%s] not found", stream.DevSessionID)
		return
	}
	sessionDev, ok := _sessionDev.(*Session)
	if !ok {
		err = fmt.Errorf("device session convert failed")
		return
	}
	sipDev := server.GetDevice(stream.DeviceID)
	if sipDev == nil {
		err = fmt.Errorf("device[%s] not found", stream.DeviceID)
		return
	}
	toDev := fmt.Sprintf("%s@%s", stream.SenderID, server.Realm)

	reqURI := fmt.Sprintf("%s@%s:%d;transport=%s", stream.SenderID, sipDev.Transport.RemoteIP, sipDev.Transport.RemotePort, strings.ToLower(sipDev.Transport.Protocol)) //c *Transport,
	reqByeDev, err := server.MakeRequest("BYE", reqURI, toDev, "")
	if err != nil {
		return
	}
	reqByeDev.SetHeader("Call-ID", strings.TrimSpace(sessionDev.CallID))
	reqByeDev.SetHeader("From", strings.TrimSpace(sessionDev.From))
	reqByeDev.SetHeader("To", strings.TrimSpace(sessionDev.To))
	reqByeDev.SetHeader("Contact", contact)
	err = reqByeDev.SendByTransport(sipDev.Transport)
	if err != nil {
		return
	}
	_, err = server.WaitAck(sessionDev.CallID, reqByeDev.HeaderMap["CSeq"])
	server.SessionCache.Delete(stream.DevSessionID)
	return
}

func (server *Server) GetStreamFromCache(streamid string) (stream *Stream) {
	_stream, ok := server.StreamCache.Get(streamid)
	if !ok {
		return
	}
	stream, _ = _stream.(*Stream)
	return
}

func (server *Server) SaveStreamToCache(streamid string, stream *Stream) {
	server.StreamCache.Set(streamid, stream, cache.NoExpiration)
}

